package img

import (
	"fmt"
	"image"
	"image/draw"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/image/webp"

	"github.com/gabriel-vasile/mimetype"
	"github.com/nfnt/resize"
	"github.com/nxshock/colorcrop"
)

const max_side float32 = 600

type Img struct {
    Link string
    FullPath string
    Extension string
}

func NewImg(link, storepath, name string, orderNum int) Img {
    localName := name + fmt.Sprintf("-%02d", orderNum)
    return Img{
        Link: link,
        FullPath: filepath.Join(storepath, localName),
    }
}

func writeImage(data image.Image, usrPath, ext string) (err error) {
    out, err := os.OpenFile(usrPath, os.O_WRONLY, os.ModeAppend)
    if err != nil {
        panic(fmt.Errorf("[writeImage]> path: %v:\n%v\n", usrPath, err))
    }
    defer out.Close()

    log.Printf("(%v) Запись в %v", ext, usrPath)
    switch ext {
    case "jpeg": err = jpeg.Encode(out, data, &jpeg.Options{ Quality: 100})
    case "png": err = png.Encode(out, data)
    case "gif": err = gif.Encode(out, data, &gif.Options{ NumColors: 256 })
    }

    if err != nil {
        log.Printf("Ошибка при записи файла:\n%v\n", err)
    } else {
        log.Println("Файл успешно записан")
    }

    return
}

func readImage(usrPath, ext string) (data image.Image, err error) {
    in, err := os.OpenFile(usrPath, os.O_RDONLY, os.ModeAppend)
    defer in.Close()

    switch ext {
    case "jpeg": data, err = jpeg.Decode(in)
    case "png": data, err = png.Decode(in)
    case "gif": data, err = gif.Decode(in)
    }

    if err != nil {
        log.Printf("Ошибка при чтении файла:\n%v\n", err)
    } else {
        log.Println("Файл успешно прочитан")
    }

    return
}

func (im *Img) generateMask() (mask image.Image, err error) {
    data, err := readImage(im.FullPath, im.Extension)
    if err != nil {
        return
    }
    interMask := image.NewNRGBA(data.Bounds())

    // Нужно для того, чтобы "конвертировать" из других цветовых моделей.
    bufferPic := image.NewNRGBA(data.Bounds())
    draw.Draw(bufferPic, bufferPic.Bounds(), data, bufferPic.Bounds().Min, draw.Src)
    pixels := bufferPic.Pix

    for i := range pixels {
        if (i+1) % 4 == 0 {
            interMask.Pix[i-3] = pixels[i]
            interMask.Pix[i-2] = pixels[i]
            interMask.Pix[i-1] = pixels[i]
            interMask.Pix[i] = 255
        }
    }
    mask = interMask
    log.Println("Маска создана")
    return
}

func (im *Img) fillBackground() (err error) {
    data, err := readImage(im.FullPath, im.Extension)
    if err != nil {
        panic(err)
    }
    background := image.NewRGBA(data.Bounds())
    mask, err := im.generateMask()
    if err != nil {
        return
    }

    for i := range background.Pix {
        background.Pix[i] = uint8(255)
    }

    draw.DrawMask(background, data.Bounds(), data, data.Bounds().Min, mask, mask.Bounds().Min, draw.Over)
    log.Println("Задник залит белым цветом")

    err = writeImage(background, im.FullPath, im.Extension)
    if err != nil {
        panic(err)
    }

    return
}

func (im *Img) ConvertToJPEG(override bool) (err error) {
    if im.Extension == "jpeg" {
        err = fmt.Errorf("[convert]> Изображение уже в формате jpeg")
        log.Println(err)
        return
    }

    newName := im.FullPath[:len(im.FullPath)-4]+".jpeg"
    if _, err = os.Stat(newName); err == nil && !override {
        err = fmt.Errorf("Файл '%s' существует, но параметр 'override' запрещает его перезаписывать\n", newName)
        log.Print(err)
        fmt.Print(err)
        return
    }

    im.fillBackground()
    data, err := readImage(im.FullPath, im.Extension)
    if err != nil {
        panic(err)
    }
    
    err = os.Rename(im.FullPath, newName)
    if err != nil {
        log.Printf("[convert]> Ошибка при переименовании файла:\n%v\n", err)
        return
    }
    (*im).FullPath = newName
    (*im).Extension = "jpeg"
    log.Printf("[convert]> Новый полный путь до файла:\n%v\n", im.FullPath)

    err = writeImage(data, im.FullPath, im.Extension)
    if err != nil {
        panic(err)
    }

    log.Println("[convert]> Файл сконвертирован в jpeg")
    return
}

func (im *Img) TrimImage() (err error) {
    data, err := readImage(im.FullPath, im.Extension)
    if err != nil {
        log.Printf("[trim]> Ну, я тут точно ошибся:\n%v\n", err)
    }

    base := image.Transparent
    if im.Extension == "jpeg" {
        base = image.White
    }

    cropped := colorcrop.Crop(data, base, 0.1)

    err = writeImage(cropped, im.FullPath, im.Extension)
    if err != nil {
        panic(err)
    }
    log.Println("[trim]> Пустые области картинки успешно обрезаны")

    return
}

func (im *Img) Scale() (err error) {
    data, err := readImage(im.FullPath, im.Extension)
    if err != nil {
        panic(err)
    }
    fWidth := float32(data.Bounds().Dx())
    fHeight := float32(data.Bounds().Dy())

    if fHeight > fWidth {
        fWidth /= (fHeight / max_side)
        fHeight = max_side
    } else if fHeight < fWidth {
        fHeight /= (fWidth / max_side)
        fWidth = max_side
    } else {
        fHeight = max_side
        fWidth = max_side
    }

    log.Printf("[scale]> Посчитаны нужные размеры для картинки: %.0fx%.0f\n", fWidth, fHeight)

    data = resize.Resize(uint(fWidth), uint(fHeight), data, resize.Bilinear)

    err = writeImage(data, im.FullPath, im.Extension)
    if err != nil {
        err = fmt.Errorf("[scale]> Не удалось конвертировать картинку:\n%v\n", err)
        log.Println(err)
        return
    }
    log.Println("[scale]> Изображение масштабировано")

    return
}

func (im *Img) MakeSquare() (err error) {
    data, err := readImage(im.FullPath, im.Extension)
    if err != nil {
        panic(err)
    }
    x0 := data.Bounds().Min.X
    y0 := data.Bounds().Min.Y

    x1 := data.Bounds().Max.X
    y1 := data.Bounds().Max.Y

    xSide := data.Bounds().Dx()
    ySide := data.Bounds().Dy()

    if xSide > ySide {
        middleOfLowerSide := y1 - ySide/2
        y0 = middleOfLowerSide - xSide/2
        y1 = middleOfLowerSide + xSide/2
    } else if xSide < ySide {
        middleOfLowerSide := x1 - xSide/2
        x0 = middleOfLowerSide - ySide/2
        x1 = middleOfLowerSide + ySide/2
    } else {
        err = fmt.Errorf("[square]> Картинка уже квадратная: %dx%d\n", xSide, ySide)
        log.Print(err)
        return
    }

    log.Printf("[square]>\nНовые размеры картинки: %vx%v\nТочки картинки:\nx0: %v\ty0: %v\n\tx1: %v\ty1: %v\n", x1 - x0, y1 - y0, x0, y0, x1, y1)
    squareData := image.NewNRGBA(image.Rect(0, 0, x1 - x0, y1 - y0))

    for i := range squareData.Pix {
        squareData.Pix[i] = 255
    }

    draw.Draw(squareData, squareData.Bounds(), data, image.Point{ X: x0, Y: y0}, draw.Src)
    err = writeImage(squareData, im.FullPath, im.Extension)
    if err != nil {
        panic(err)
    }

    log.Println("[square]> Контент изображения отцентрован, длина и ширина картинки теперь равны")
    return
}

func (im *Img) DownloadImage(override bool) (err error) {
    req, err := http.NewRequest("GET", im.Link, nil)
    if err != nil {
        log.Fatal(err)
    }

    // Некоторые сайты выкидывают ошибку 403 и недают качать файл, им нужен юзер агент
    req.Header.Set("User-Agent", "Golang_Spider_Bot/3.0")

    resp, err := http.Get(im.Link)
    if err != nil {
        err = fmt.Errorf("[DownloadImage]> Ошибка при скачивании файла:\n%v\n", err)
    }
    defer resp.Body.Close()

    bytes, err := io.ReadAll(resp.Body)
    if err != nil {
        err = fmt.Errorf("[DownloadImage]> Не удалось считать файл:\n%v\n", err)
        return
    }

    resp, err = http.Get(im.Link)
    if err != nil {
        err = fmt.Errorf("[DownloadImage]> Ошибка при скачивании файла:\n%v\n", err)
    }
    defer resp.Body.Close()

    mimeType := strings.Split(http.DetectContentType(bytes), "/")
    log.Printf("[DownloadImage]> Тип полученного файла:%v\n", mimeType)

    if mimeType[0] == "image" && mimeType[1] != "webp" {
        (*im).Extension = mimeType[1]
        (*im).FullPath += "." + im.Extension
    } else if mimeType[1] == "webp" {
        log.Println("[DownloadImage]> Файл типа 'webp' будет сконвертирован в 'png'")
        (*im).Extension = "png"
        (*im).FullPath += "." + im.Extension
    } else {
        err = fmt.Errorf("[DownloadImage]> Полученный файл не является изображением: %v\n", mimeType)
        log.Print(err)
        return
    }

    err = im.saveImage(override, resp.Body)

    return
}

func (im *Img) LocalCopyImage(override bool) (err error) {
    if _, err = os.Stat(im.Link); err != nil {
        err = fmt.Errorf("[LocalCopyImage]> Не удалось открыть '%s':\n%v\n", im.Link, err)
        log.Print(err)
        return
    }

    in, err := os.Open(im.Link)
    if err != nil {
        log.Printf("[LocalSaveImage]> Не удалось открыть файл:\n%v\n", err)
        return
    }

    mtype, err := mimetype.DetectFile(im.Link)
    if err != nil {
        err = fmt.Errorf("[LocalSaveImage]> Не удалось определить тип файла:\n%v\n", err)
        log.Print(err)
        return
    }
    mimeType := strings.Split(mtype.String(), "/")[0]
    fileType := strings.Split(mtype.String(), "/")[1]

    if mimeType == "image" && fileType != "webp" {
        (*im).Extension = fileType
        (*im).FullPath += "." + im.Extension
    } else if fileType == "webp" {
        log.Println("[LocalSaveImage]> Файл типа 'webp' будет сконвертирован в 'png'")
        (*im).Extension = "png"
        (*im).FullPath += "." + im.Extension
    } else {
        err = fmt.Errorf("[LocalSaveImage]> Полученный файл не является изображением: %v\n", mimeType)
        log.Print(err)
        return
    }

    err = im.saveImage(override, in)

    return
}

func (im *Img) saveImage(override bool, meReader io.Reader) (err error) {
    currDir := filepath.Dir(im.FullPath)
    if _, err = os.Stat(im.FullPath); err == nil && !override {
        err = fmt.Errorf("Файл '%s' в папке '%s' существует. Программе не указано разрешение перезаписывать существующие файлы\n", filepath.Base(im.FullPath), currDir)
        log.Print(err)
        return

    }

    if currDir != "" && currDir != "." && currDir != "." + string(os.PathSeparator) {
        err = os.MkdirAll(currDir, 0754)
        if err != nil {
            err = fmt.Errorf("[saveimage]> Ошибка при создании папки '%s':\n%v", currDir, err)
            return
        }
    }

    out, err := os.Create(im.FullPath)
    if err != nil {
        err = fmt.Errorf("[saveimage]> Ошибка при создании файла:\n%v\n", err)
        return
    } else {
        log.Println("[saveimage]> Файл создан")
    }

    _, err = io.Copy(out, meReader)
    if err != nil {
        err = fmt.Errorf("[saveimage]> Ошибка при сохранении файла на диск:\n%v\n", err)
        return
    } else {
        log.Println("[saveimage]> Файл сохранён на диск")
    }
    out.Close()

    mtype, err := mimetype.DetectFile(im.FullPath)
    if err != nil {
        err = fmt.Errorf("Не удалось определить MIME файла: %v\n", mtype.String())
        log.Print(err)
        return
    }
    fileType := mtype.Extension()[1:]

    if fileType == "webp" {
        out, err = os.OpenFile(im.FullPath, os.O_RDONLY, 0666)

        if err != nil {
            log.Fatalf("[saveimage]> Не удалось открыть картинку на чтение:\n%v\n", err)
        }

        data, err := webp.Decode(out)
        if err != nil {
            log.Fatalf("[saveimage]> Не удалось декодировать картинку:\n%v\n", err)
        }
        out.Close()

        out, err = os.OpenFile(im.FullPath, os.O_WRONLY, 0666)
        if err != nil {
            log.Fatalf("[saveimage]> Не удалось открыть картинку на запись:\n%v\n", err)
        }
        defer out.Close()

        err = png.Encode(out, data)
        if err != nil {
            log.Fatalf("[saveimage]> Не удалось конвертировать картинку:\n%v\n", err)
        }
    } else if err != nil {
        log.Print(err)
        return err
    }

    return
}
